/*
  author Sylvain Bertrand <digital.ragnarok@gmail.com>
  Protected by GNU Affero GPL v3 with some exceptions.
  See README at root of alga tree.
*/
#include <linux/i2c.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <asm/uaccess.h>

#include <alga/alga.h>
#include <alga/pixel_fmts.h>
#include <alga/timing.h>
#include <alga/dp.h>
#include <alga/edid.h>
#include <alga/amd/atombios/atb.h>
#include <alga/amd/atombios/dce.h>
#include <alga/amd/dce4/dce4.h>
#include <alga/amd/dce4/dce4_dev.h>

#include "dce4.h"
#include "dp_link_train.h"
#include "crtc.h"


/* code related to the displayport sink, usually the display */

int sink_timings(struct dce4 *dce, unsigned i,
				struct alga_timing (*ts)[ALGA_TIMINGS_MAX])
{
	int r;

	if (dce->dps[i].edid == NULL) {
		alga_dp_safe_timing(ts);
		return 0;
	}
	r = alga_edid_timings(dce->ddev.dev, dce->dps[i].edid, ts);
	if (r != 0)
		return -DCE4_ERR;
	return 0;
}

void sink_pixel_fmts(struct dce4 *dce, unsigned i,
			enum alga_pixel_fmt (*fmts)[ALGA_PIXEL_FMTS_MAX])
{
	int bpc;
	unsigned j;

	for (j = 0; j < ALGA_PIXEL_FMTS_MAX; ++j)
		(*fmts)[i] = ALGA_PIXEL_FMT_INVALID;

	if (dce->dps[i].edid == NULL) {
		(*fmts)[0] = ALGA_ARGB6666;
		return;
	}

	bpc = alga_edid_bpc(dce->ddev.dev, dce->dps[i].edid);	
	if (bpc == 0) {
		(*fmts)[0] = ALGA_ARGB6666;
		return;
	}

	j = 0;
	switch (bpc) { /* we are not getting above ALGA_PIXEL_FMTS_MAX */
	case 16:
	case 14:
	case 12:
	case 10:
		(*fmts)[j++] = ALGA_ARGB2101010;
	case 8:
		(*fmts)[j++] = ALGA_ARGB8888;
	case 6:
		(*fmts)[j++] = ALGA_ARGB6666;
	}
}

static unsigned sink_link_lanes_n(struct dce4 *dce, unsigned i)
{
	switch (dce->dps[i].dpcd_info[DPCD_MAX_LANE_COUNT]
						& DPCD_MAX_LANE_COUNT_MASK) {
	case 0x1:
		return 1;
	case 0x2:
		return 2;
	case 0x4:
		return 4;
	}
	unreachable();
}

/*
 * link configuration heuristics
 * help to avoid signal power shortage side effects and more, in theory...
 * Others heuristics would be a sequence of minimum number of lanes for
 * each supported link rate able to handle the video mode bandwidth.
 */
static void link_rate_cfg(struct dce4 *dce, unsigned i, struct db_fb *db_fb)
{
	enum link_rate max;
	unsigned link_rate_bw_max;
	unsigned bw;
	unsigned long bw_overhead;

	/* Mbits per second for the timing/pixel format (pixel clk is in kHz) */
	bw = db_fb->timing.pixel_clk * 8 * alga_pixel_fmts_sz[db_fb->pixel_fmt]
									/ 1000;
	/* +20 % average overhead: (8b/10b) encoding and more... */
	bw_overhead = 120 * bw / 100;

	/*
	 * the transmitter has 4 lanes, then the limiting factor is the
	 * sink lane number
	 */
	dce->dps[i].lanes_n = sink_link_lanes_n(dce, i);

	/* max link rate sink and transmitter can support */
	if (dce->dps[i].dpcd_info[DPCD_MAX_LINK_RATE]
					>= dce->dps[i].trans_link_rate_max)
		max = dce->dps[i].trans_link_rate_max;
	else
		max = (enum link_rate)(
				dce->dps[i].dpcd_info[DPCD_MAX_LINK_RATE]);

	/*
	 * heuristics step one: we want to spread that bandwidth plus the
	 * overhead on maximum lanes at the lowest link rate	
	 */
	link_rate_bw_max = GHZ_1_62 * DP_LINK_RATE_UNIT_MHZ
							* dce->dps[i].lanes_n;
	if ((bw_overhead < link_rate_bw_max) || (GHZ_1_62 == max)) {
		dce->dps[i].link_rate = GHZ_1_62;
	} else {
		link_rate_bw_max = GHZ_2_7 * DP_LINK_RATE_UNIT_MHZ
							* dce->dps[i].lanes_n;
		if ((bw_overhead < link_rate_bw_max) || (GHZ_2_7 == max)) {
			dce->dps[i].link_rate = GHZ_2_7;
		} else {
			link_rate_bw_max = GHZ_5_4 * DP_LINK_RATE_UNIT_MHZ
							* dce->dps[i].lanes_n;
			if ((bw_overhead < link_rate_bw_max)
							|| (GHZ_5_4 == max)) {
				dce->dps[i].link_rate = GHZ_5_4;
			}
		}
	}

	/*
	 * heuristics step two : see if we can reduce the number of lanes: 1 or
	 * 2. dce->dps[i].lanes is set to the max number of sink lanes.
	 */
	if ((dce->dps[i].lanes_n == 2) || (dce->dps[i].lanes_n == 4)) {
		/* bw plus overhead fits on 1 lane? */
		link_rate_bw_max = dce->dps[i].link_rate
						* DP_LINK_RATE_UNIT_MHZ * 1;
		if (bw_overhead < link_rate_bw_max) {
			dce->dps[i].lanes_n = 1;
		} else if (dce->dps[i].lanes_n == 4) {
			/* bw plus overhead fits on 2 lanes? */
			link_rate_bw_max = dce->dps[i].link_rate
						* DP_LINK_RATE_UNIT_MHZ * 2;
			if (bw_overhead < link_rate_bw_max) {
				dce->dps[i].lanes_n = 2;
			}
		}
	}
	dev_info(dce->ddev.dev, "dce4: dp%u link selected configuration is"
				" %u lanes at %s\n", i, dce->dps[i].lanes_n,
					link_rate_names(dce->dps[i].link_rate));
}

int sink_mode_set(struct dce4 *dce, unsigned i, struct db_fb *db_fb)
{
	struct atombios *atb;
	int r;

	atb = dce->ddev.atb;

	link_rate_cfg(dce, i, db_fb); /* heuristics for link rate/lanes cfg */
	
	atb_lock(atb, true);

	/* prepare encoder and transmitter */
	r = atb_trans_link_output_off(atb, i);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to switch off "
						"transmitter link output\n", i);
		r = -DCE4_ERR;
		goto unlock_atb;
	}

	r = atb_enc_video(atb, i, false);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to switch off "
						"encoder video output\n", i);
		r = -DCE4_ERR;
		goto unlock_atb;
	}

	if (dce->dps[i].edp) {
		r = atb_trans_link_pwr(atb, i, true);
		if (r != 0) {
			dev_err(dce->ddev.dev, "dce4:dp%u: unable to enable "
						"power pins on connector\n", i);
			r = -DCE4_ERR;
			goto unlock_atb;
		}
		msleep(300);/* follow timing specs from AMD code */
	}

	r = atb_enc_crtc_src(atb, i);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to link crtc and "
						"encoder together\n", i);	
		r = -DCE4_ERR;
		goto unlock_atb;
	}

	/* prepare crtc */
	r = atb_crtc_lock(atb, i, true);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to lock the crtc\n",
									i);
		r = -DCE4_ERR;
		goto unlock_atb;
	}

	r = atb_crtc_blank(atb, i, true);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to blank the crtc\n",
									i);
		r = -DCE4_ERR;
		goto unlock_atb;
	}

	r = atb_crtc_mem_req(atb, i, false);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to disable crtc "
							"memory requests\n", i);
		r = -DCE4_ERR;
		goto unlock_atb;
	}

	r = atb_crtc(atb, i, false);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to disable the "
								"crtc\n", i);
		r = -DCE4_ERR;
		goto unlock_atb;
	}

	/* crtc mode set */
	r = atb_crtc_virtual_pixel_clk(atb, i, db_fb->timing.pixel_clk);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to program the "
						"virtual pixel clock\n", i);
		r = -DCE4_ERR;
		goto unlock_atb;
	}

	r = atb_crtc_timing(atb, i, &db_fb->timing);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to program timings\n",
									i);
		r = -DCE4_ERR;
		goto unlock_atb;
	}

	r = crtc_fb(dce, i, db_fb);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to setup the frame "
								"buffer\n", i);
		goto unlock_atb;
	}

	r = atb_crtc_overscan(atb, i);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to setup the "
							"overscan\n", i);
		r = -DCE4_ERR;
		goto unlock_atb;
	}

	r = atb_crtc_scaler(atb, i);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to setup the "
								"scaler\n", i);
		r = -DCE4_ERR;
		goto unlock_atb;
	}

	/* encoder and transmitter mode set */
	r = atb_trans_link_off(atb, i);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to disable the "
						"transmitter link\n", i);
		r = -DCE4_ERR;
		goto unlock_atb;
	}

	r = atb_enc_setup(atb, i, dce->dps[i].lanes_n,
		dce->dps[i].link_rate, alga_pixel_fmts_bpc[db_fb->pixel_fmt],
						db_fb->timing.pixel_clk);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to setup the "
								"encoder\n", i);
		r = -DCE4_ERR;
		goto unlock_atb;
	}

	r = atb_trans_link_on(atb, i, dce->dps[i].lanes_n,
						db_fb->timing.pixel_clk);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to enable the "
						"transmitter link\n", i);
		r = -DCE4_ERR;
		goto unlock_atb;
	}

	/* crtc commit */
	r = atb_crtc(atb, i, true);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to enable the "
								"crtc\n", i);
		r = -DCE4_ERR;
		goto unlock_atb;
	}

	r = atb_crtc_mem_req(atb, i, true);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to enable the crtc "
							"memory requests\n", i);
		r = -DCE4_ERR;
		goto unlock_atb;
	}

	r = atb_crtc_blank(atb, i, false);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to unblank the "
								"crtc\n", i);
		r = -DCE4_ERR;
		goto unlock_atb;
	}

	crtc_lut(dce, i);

	r = atb_crtc_lock(atb, i, false);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to unlock the crtc\n",
									i);
		r = -DCE4_ERR;
		goto unlock_atb;
	}

	/* encoder and transmitter commit */
	r = atb_trans_link_output_on(atb, i, dce->dps[i].lanes_n,
						db_fb->timing.pixel_clk);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to enable the "
						"transmitter link output\n", i);
		r = -DCE4_ERR;
		goto unlock_atb;
	}

	/* XXX: skipped switching on again power */

	r = dp_link_train(dce, i);
	if (r < 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to train the link\n",
									i);
		goto unlock_atb;
	}

	r = atb_enc_video(atb, i, true);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce4:dp%u: unable to enable the video "
						"output on encoder\n", i);
		r = -DCE4_ERR;
		goto unlock_atb;
	}
unlock_atb:
	atb_lock(atb, false);
	return r;
}
